package $3exceptionhandler;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class FnLog4j {
    static final Log log = LogFactory.getLog(FnLog4j.class);
    public static void main(String[] args) {
        /**
         * 使用Log4j
         * Log4j是一种非常流行的日志框架
         * Log4j是一个组件化设计的日志系统，它的架构大致如下
         * log.info("User signed in.");
         * ├──>│ Appender │───>│  Filter  │───>│  Layout  │───>│ Console  │
         * ├──>│ Appender │───>│  Filter  │───>│  Layout  │───>│   File   │
         * └──>│ Appender │───>│  Filter  │───>│  Layout  │───>│  Socket  │
         * 
         * 当我们使用Log4j输出一条日志时，Log4j自动通过不同的Appender把同一条日志输出到不同的目的地。
         * console：输出到屏幕；
         * file：输出到文件；
         * socket：通过网络输出到远程计算机；
         * jdbc：输出到数据库
         * 
         * 在输出日志的过程中，通过Filter来过滤哪些log需要被输出，哪些log不需要被输出。例如，仅输出ERROR级别的日志。
         * 最后，通过Layout来格式化日志信息，例如，自动添加日期、时间、方法名称等信息。
         * 
         * 上述结构虽然复杂，但我们在实际使用的时候，并不需要关心Log4j的API，而是通过配置文件来配置它。
         * 以XML配置为例，使用Log4j的时候，我们把一个log4j2.xml的文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志。
         * 虽然配置Log4j比较繁琐，但一旦配置完成，使用起来就非常方便。
         * 凡是INFO级别的日志，会自动输出到屏幕，而ERROR级别的日志，不但会输出到屏幕，还会同时输出到文件。
         * 并且，一旦日志文件达到指定大小（1MB），Log4j就会自动切割新的日志文件，并最多保留10份。
         * 有了配置文件还不够，因为Log4j也是一个第三方库，我们需要从这里下载Log4j，解压后，把以下3个jar包放到classpath中
         * log4j-api-2.x.jar
         * log4j-core-2.x.jar
         * log4j-jcl-2.x.jar
         * 因为Commons Logging会自动发现并使用Log4j，所以，把上一节下载的commons-logging-1.2.jar也放到classpath中。
         * 要打印日志，只需要按Commons Logging的写法写，不需要改动任何代码，就可以得到Log4j的日志输出
         * 
         * 最佳实践
         * 在开发阶段，始终使用Commons Logging接口来写入日志，并且开发阶段无需引入Log4j。
         * 如果需要把日志写入文件，只需要把正确的配置文件和Log4j相关的jar包放入classpath，就可以自动把日志切换成使用Log4j写入，无需修改任何代码。
         * 
         * 小结
         * 通过Commons Logging实现日志，不需要修改代码即可使用Log4j；
         * 使用Log4j只需要把log4j2.xml和相关jar放入classpath；
         * 如果要更换Log4j，只需要移除log4j2.xml和相关jar；
         * 只有扩展Log4j时，才需要引用Log4j的接口（例如，将日志加密写入数据库的功能，需要自己开发）。
         */
        
        log.info("Start process...");
        try {
            "".getBytes("invalidCharsetName");
        } catch (UnsupportedEncodingException e) {
            log.error("Invalid encoding.", e);
        }
        log.info("Process end.");
        
        Log log = LogFactory.getLog(FnCommonsLogging.class);
        log.info("start...");
        log.warn("end.");
        
        foo();
        
        Personlog xiaoming = new Personlog();
        xiaoming.foo();
        
        Studentlog xiaohong = new Studentlog();
        xiaohong.bar();
        
        // 此外，除了标准的info(String)外，还提供了一个非常有用的重载方法：info(String, Throwable)，这使得记录异常更加简单。
        try {
            process1();
        } catch (Exception e) {
            log.error("got exception!", e);
        } 
        
    }
    // 使用Commons Logging时，如果在静态方法中引用Log，通常直接定义一个静态类型变量
    static void foo() {
        log.info("静态方法foo");
    }
    
    // 由于Java类的动态特性，子类获取的log字段实际上相当于LogFactory.getLog(Student.class)，但却是从父类继承而来，并且无需改动代码。
    private static void process1() throws IOException {
        System.out.println("process1 run");
        process2();
    }
    private static void process2() {
        // TODO Auto-generated method stub
        Integer.parseInt(null); // 会抛出NumberFormatException
    }
}
// 在实例方法中引用Log，通常定义一个实例变量
// 实例变量log的获取方式是LogFactory.getLog(getClass())
// 这种方式有个非常大的好处，就是子类可以直接使用该log实例。
class Personlog1 {
    protected final Log log = LogFactory.getLog(getClass());

    void foo() {
        log.info("实例foo");
    }
}
// 在子类中使用父类实例化的log:
class Studentlog1 extends Personlog {
    void bar() {
        log.info("子类bar");
    }
}